#pragma once

#include "Draw.hpp"
#include <iostream>
#include <vector>
#include <fstream>
#include <string>
#include <unordered_map>
#include <map>
#include <queue>

using namespace std;
// val >> path >> time
class TSystem
{
private:
    using pivv = pair<int, vector<vector<int>>>;
    string PlaseTXT, PlanePTXT, TrainPTXT, BusPTXT, PlaneTTXT, TrainTTXT, BusTTXT;
    vector<vector<vector<int>>> PlanePath, TrainPath, BusPath, PlaneTime, TrainTime, BusTime;
    unordered_map<int, string> NumToPlase;
    unordered_map<string, int> PlaseToNum;
    vector<pair<double, double>> Coords;
    int width = 0, height = 0;
    Draw DrawBoard;

    int PlaseNum = 0;
    vector<int> PathNums, TimeNums;

    void OpenPicture(int index)
    {
        _open_picture(index);
    }

    void DrawCoords()
    {
        for (int i = 0; i < PlaseNum; i++)
        {
            auto &cd = Coords[i];
            int a = cd.first, b = cd.second;
            DrawBoard.DrawCoord(a, b);
            DrawBoard.DrawWord(a, b, NumToPlase[i]);
        }
    }

    void DrawPath(const vector<vector<int>> &path)
    {
        int n = path.size();
        if (n < 2)
            return;
        pair<double, double> cd1 = Coords[path[0]];

        for (int i = 1; i < n; i++)
        {
            pair<double, double> cd2 = Coords[path[i][0]];
            DrawBoard.DrawLine(cd1.first, cd1.second, cd2.first, cd2.second, path[i][1]);
            cd1 = cd2;
        }
    }

    void ReadPath(const string &name, vector<vector<vector<int>>> &edges, int med)
    {
        fstream TXT;
        TXT.open(name);
        int &n = PathNums[med];
        TXT >> n;

        string p1, p2;
        int val, path, time;
        for (int i = 0; i < n; i++)
        {
            TXT >> p1 >> p2 >> val >> path >> time;
            edges[PlaseToNum[p1]][PlaseToNum[p2]] = {1, val, path, time};
        }

        TXT.close();
    }

    void ReadTime(const string &name, vector<vector<vector<int>>> &times, int med)
    {
        fstream TXT;
        TXT.open(name);
        int &n = TimeNums[med];
        TXT >> n;

        string p1, p2;
        int time, cnt;
        for (int i = 0; i < n; i++)
        {
            TXT >> p1 >> p2;
            TXT >> cnt;
            int x = PlaseToNum[p1], y = PlaseToNum[p2];
            while (cnt--)
            {
                TXT >> time;
                times[x][y].push_back(time);
            }
        }

        TXT.close();
    }

    void WritePath(const string &name, vector<vector<vector<int>>> &edges, int med)
    {
        ofstream TXT;
        TXT.open(name);
        TXT << PathNums[med] << endl;
        for (int i = 0; i < PlaseNum; i++)
        {
            for (int j = 0; j < PlaseNum; j++)
            {
                if (edges[i][j][0])
                {
                    TXT << NumToPlase[i] << " " << NumToPlase[j] << " "
                        << edges[i][j][1] << " " << edges[i][j][2] << ' ' << edges[i][j][3] << endl;
                }
            }
        }
        TXT.close();
    }

    void WriteTime(const string &name, vector<vector<vector<int>>> &times, int med)
    {
        ofstream TXT;
        TXT.open(name);

        TXT << TimeNums[med] << endl;
        for (int i = 0; i < PlaseNum; i++)
        {
            for (int j = 0; j < PlaseNum; j++)
            {
                int n = times[i][j][0];
                if (n <= 0)
                    continue;
                TXT << NumToPlase[i] << " " << NumToPlase[j] << ' ';
                TXT << n << " ";
                for (int k = 1; k <= n; k++)
                {
                    TXT << times[i][j][k] << ' ';
                }
                TXT << endl;
            }
        }
        TXT.close();
    }

    void WriteCoords()
    {
        ofstream TXT;
        TXT.open(PlaseTXT);

        TXT << PlaseNum << endl;
        for (int i = 0; i < PlaseNum; i++)
        {
            string &city = NumToPlase[i];
            double a = Coords[i].first, b = Coords[i].second;
            TXT << city << ' ' << a << ' ' << b << endl;
        }

        TXT.close();
    }

    void _PrintPath(vector<vector<vector<int>>> &edges)
    {
        for (int i = 0; i < PlaseNum; i++)
        {
            for (int j = 0; j < PlaseNum; j++)
            {
                if (edges[i][j][0])
                {
                    DrawBoard.DrawLine(Coords[i].first, Coords[i].second, Coords[j].first, Coords[j].second);
                }
            }
        }
    }

    int CountTime(int i, int j, int start_time, vector<vector<vector<int>>>& edges)
    {
        int time = start % MAX_Minute;
        vector<int>& v = edges[i][j];
        if(time > v.back()) return start_time + MAX_Minute - time + v[0];
        
        int left = 0, right = v.size() - 1;
        while(left < right)
        {
            int mid = (left + right) / 2;
            if(v[mid] < time) left = mid + 1;
            else right = mid;
        }
        return start_time + v[left] - time;
    }

    pivv _FindPath(const string &p1, const string &p2, int vehicle_med, int med, int start_time)
    {
        // med 1 2 3 ->  val path time
        int a = PlaseToNum[p1], b = PlaseToNum[p2];
        int ret = 0xfffffff;
        vector<int> vis(PlaseNum);
        auto cmp = [&](const pivv &x, const pivv &y) -> bool
        {
            return x.first > y.first;
        };
        priority_queue<pivv, vector<pivv>, decltype(cmp)> q(cmp);
        vector<vector<int>> path;
        // plase_num, vehicle_med, time
        path.push_back({a, 0, 0})
        q.push(make_pair(0, path));
        vis[a] = 7;

        auto bfs = [&](vector<vector<vector<int>>> &edges, int x, pivv& node, int med) -> void
        {
            int i = node.second.back()[0];
            int val = node.first;
            int start_time = node.second.back()[2];
            for (int j = 0; j < PlaseNum; j++)
            {
                if (vis[j] | x)
                    continue;
                if (edges[i][j][0])
                {
                    vector<vector<int>> tmp = node.second;
                    tmp.push_back({j, x, CountTime(i, j, start_time, edges)});
                    if(med != 3)
                        q.push(make_pair(val + edges[i][j][med], tmp));
                    else    
                        q.push(make_pair(tmp.back()[3], tmp));
                    vis[j] |= x;
                }
            }
        };

        while (!q.empty())
        {
            pivv node = q.top();
            q.pop();
            if (node.second.back()[0] == b)
            {
                ret = node.first;
                path = node.second;
                break;
            }

            int x = 1;
            while (x <= vehicle_med)
            {
                if (x | med == 0)
                    continue;
                switch (x)
                {
                case 1:
                    bfs(PlanePath, x);
                    break;
                case 2:
                    bfs(TrainPath, x);
                    break;
                case 4:
                    bfs(BusPath, x);
                    break;
                default:
                    break;
                }
                x <<= 1;
            }
        }
        return make_pair(ret, path);
    }

public:
    TSystem(int w, int h)
        : PlaseTXT("./Plase.txt"), PlanePTXT("./PlaneP.txt"), TrainPTXT("./TrainP.txt"), BusPTXT("./BusP.txt"),
          PlaneTTXT("./PlaneT.txt"), TrainTTXT("./TrainT.txt"), BusTTXT("./BusT.txt"), width(w), height(h)
    {
        TimeNums.resize(3);
        PathNums.resize(3);
        DrawBoard.Start(width, height);
        fstream PAST;
        PAST.open(PlaseTXT);
        PAST >> PlaseNum;
        PlanePath = TrainPath = BusPath = vector<vector<vector<int>>>(PlaseNum, vector<vector<int>>(PlaseNum, vector<int>(4)));
        PlaneTime = TrainTime = BusTime = vector<vector<vector<int>>>(PlaseNum, vector<vector<int>>(PlaseNum, vector<int>(1)));
        string path;
        double x, y;
        for (int i = 0; i < PlaseNum; i++)
        {
            PAST >> path >> x >> y;
            NumToPlase[i] = path;
            PlaseToNum[path] = i;
            Coords.push_back(make_pair(x, y));
        }
        ReadPath(PlanePTXT, PlanePath, 0);
        ReadPath(TrainPTXT, TrainPath, 1);
        ReadPath(BusPTXT, BusPath, 2);
        ReadTime(PlaneTTXT, PlaneTime, 0);
        ReadTime(TrainTTXT, TrainTime, 1);
        ReadTime(BusTTXT, BusTime, 2);
        PAST.close();
        DrawCoords();
    }

    void Save()
    {
        WritePath(PlanePTXT, PlanePath, 0);
        WritePath(TrainPTXT, TrainPath, 1);
        WritePath(BusPTXT, BusPath, 2);
        WriteTime(PlaneTTXT, PlaneTime, 0);
        WriteTime(TrainTTXT, TrainTime, 1);
        WriteTime(BusTTXT, BusTime, 2);
        WriteCoords();
    }
    void ReDraw()
    {
        DrawBoard.Clear();
        DrawCoords();
    }

    void FindPath(const string &p1, const string &p2, int med, int start_time)
    {
        // about med 0 0 0 -> plane train bus
        int x = PlaseToNum[p1], y = PlaseToNum[p2];
        for (int i = 1; i <= 3; i++)
        {
            // i val path time
            vector<vector<int>> path;
            int val = 0xfffffff;

            pivv tmp = _FindPath(p1, p2, med, i, start_time);

            path = tmp.second;
            val = tmp.first;

            // if (med & 1) // Plane
            // {
            //     pivi tmp = _FindPath(p1, p2, PlanePath, i);
            //     if (val > tmp.first)
            //     {
            //         path = tmp.second;
            //         way = 1;
            //         val = tmp.first;
            //     }
            // }
            // if (med & 2) // Train
            // {
            //     pivi tmp = _FindPath(p1, p2, TrainPath, i);
            //     if (val > tmp.first)
            //     {
            //         path = tmp.second;
            //         val = tmp.first;
            //         way = 2;
            //     }
            // }
            // if (med & 4) // Bus
            // {
            //     pivi tmp = _FindPath(p1, p2, BusPath, i);
            //     if (val > tmp.first)
            //     {
            //         path = tmp.second;
            //         val = tmp.first;
            //         way = 3;
            //     }
            // }

            switch (i)
            {
            case 1:
                cout << "����˵ļ۸�" << val << "RMB" << endl;
                break;
            case 2:
                cout << "��̵�·�̣�" << val << "km" << endl;
                break;
            case 3:
                cout << "����ʱ�䣺" << val << "min" << endl;
                break;
            default:
                break;
            }
            // ����Path and Way
            DrawPath(path);
        }
    }

    void PrintPath(int med)
    {
        DrawBoard.Clear();
        DrawCoords();
        // about med 0 0 0 -> plane train bus
        if (med & 1) // Plane
        {
            _PrintPath(PlanePath);
        }
        if (med & 2) // Train
        {
            _PrintPath(TrainPath);
        }
        if (med & 4) // Bus
        {
            _PrintPath(BusPath);
        }
    }

    void AddPath(const string &p1, const string &p2, int val, int path, int time, int med, int start_time)
    {
        int x = PlaseToNum[p1], y = PlaseToNum[p2];
        switch (med)
        {
        case 1:
            PlanePath[x][y] = {1, val, path, time};
            PlaneTime[x][y].push_back(start_time);
            PathNums[0]++;
            break;
        case 2:
            TrainPath[x][y] = {1, val, path, time};
            TrainTime[x][y].push_back(start_time);
            PathNums[1]++;
            break;
        case 3:
            BusPath[x][y] = {1, val, path, time};
            BusTime[x][y].push_back(start_time);
            PathNums[2]++;
            break;
        default:
            cout << "��������Чֵ��" << endl;
            break;
        }

        // �ػ�
        PrintPath(7);
    }

    void AddTime(const string &p1, const string &p2, int time, int med)
    {
        int x = PlaseToNum[p1], y = PlaseToNum[p2];
        switch (med)
        {
        case 1:
            PlaneTime[x][y].push_back(time);
            break;
        case 2:
            TrainTime[x][y].push_back(time);
            break;
        case 3:
            BusTime[x][y].push_back(time);
            break;
        default:
            cout << "��������Чֵ��" << endl;
            break;
        }
    }

    ~TSystem()
    {
        Save();
    }
};